package cn.jolyoulu.common.redis.utils;

import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Component;

import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * Redis 排行榜工具类
 *
 * @Author: JolyouLu
 * @Date: 2023/1/19 19:43
 * @Description Redis工具类
 */
@Slf4j
@Component
public class RedisRankUtils {

    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 获取排行榜总条数
     *
     * @param key 排行榜唯一标识
     * @return 排行榜列表
     */
    @SuppressWarnings("unchecked")
    public Long rankSize(String key) {
        return redisTemplate.opsForZSet().zCard(key);
    }

    /**
     * 获取排行榜列表
     *
     * @param key      排行榜唯一标识
     * @param orderBy  排行榜排序方式
     * @param page     当前页
     * @param pageSize 分页大小
     * @return 排行榜列表
     */
    @SuppressWarnings("unchecked")
    public List<Item> rankList(String key, Integer page, Integer pageSize, OrderBy orderBy) {
        //不存在直接返回空列表
        if (!redisTemplate.hasKey(key)) {
            return Collections.emptyList();
        }
        //计算开始页
        int start = (page - 1) * pageSize;
        int end = start + pageSize - 1;
        Set<ZSetOperations.TypedTuple<String>> rank;
        if (orderBy.equals(OrderBy.ASC)) {
            rank = redisTemplate.opsForZSet().rangeWithScores(key, start, end);
        } else {
            rank = redisTemplate.opsForZSet().reverseRangeWithScores(key, start, end);
        }
        if (Objects.isNull(rank)) {
            return Collections.emptyList();
        }
        return rank.stream()
                .map(m -> {
                    Long rankNum = this.getRankByValue(key, m.getValue(), orderBy);
                    return new Item(m.getValue(), m.getScore(), rankNum);
                })
                .collect(Collectors.toList());
    }

    /**
     * 获取某个value在排行榜中的信息
     *
     * @param key     排行榜唯一标识
     * @param unique  排行榜成员的唯一标识
     * @param orderBy 排行榜排序方式
     * @return 排名信息
     */
    @SuppressWarnings("unchecked")
    public Item getItemByValue(String key, String unique, OrderBy orderBy) {
        Long rank = this.getRankByValue(key, unique, orderBy);
        Double score = redisTemplate.opsForZSet().score(key, unique);
        return new Item(unique, score, rank);
    }

    /**
     * 获取某个value在排行榜中的排名信息
     *
     * @param key     排行榜唯一标识
     * @param unique  排行榜成员的唯一标识
     * @param orderBy 排行榜排序方式
     * @return 排名0开始
     */
    @SuppressWarnings("unchecked")
    public Long getRankByValue(String key, String unique, OrderBy orderBy) {
        Long rank;
        if (orderBy.equals(OrderBy.ASC)) {
            rank = redisTemplate.opsForZSet().rank(key, unique);
        } else {
            rank = redisTemplate.opsForZSet().reverseRank(key, unique);
        }
        return rank;
    }

    /**
     * 往排行榜中添加数据
     *
     * @param key  排行榜唯一标识
     * @param item 要添加的数据
     * @return 是否成功
     */
    @SuppressWarnings("unchecked")
    public boolean set(String key, Item item) {
        return redisTemplate.opsForZSet()
                .add(key, item.getUnique(), item.getScore());
    }

    /**
     * 往排行榜中添加数据，如果不存在就添加
     *
     * @param key  排行榜唯一标识
     * @param item 要添加的数据
     * @return 是否成功
     */
    @SuppressWarnings("unchecked")
    public boolean addIfAbsent(String key, Item item) {
        return redisTemplate.opsForZSet()
                .addIfAbsent(key, item.getUnique(), item.getScore());
    }

    /**
     * 将某个unique移除出排行榜
     *
     * @param key    排行榜唯一标识
     * @param unique 排行榜成员的唯一标识
     * @return 已删除元素数量
     */
    @SuppressWarnings("unchecked")
    public Long remove(String key, String unique) {
        return redisTemplate.opsForZSet()
                .remove(key, unique);
    }

    /**
     * 累加某个unique的分数，有权重的Item慎重使用
     *
     * @param key    排行榜唯一标识
     * @param unique 排行榜成员的唯一标识
     * @param score  分数
     * @return 累加后的分数
     */
    @SuppressWarnings("unchecked")
    public Double incrScore(String key, String unique, Double score) {
        return redisTemplate.opsForZSet().incrementScore(key, unique, score);
    }

    /**
     * 累加某个unique的分数
     *
     * @param key    排行榜唯一标识
     * @param unique 排行榜成员的唯一标识
     * @param score  分数
     * @return 累加后的分数
     */
    @SuppressWarnings("unchecked")
    public Double incrScore(String key, String unique, Integer score) {
        return redisTemplate.opsForZSet().incrementScore(key, unique, score);
    }

    @Getter
    public static class Item {
        /**
         * 唯一标识
         */
        private String unique;
        /**
         * 分数
         */
        private Double score;
        /**
         * 排名 0 开始
         */
        private Long rank;

        private Item() {
        }

        private Item(String unique, Double score) {
            this.unique = unique;
            this.score = score;
        }

        private Item(String unique, Double score, Long rank) {
            this.unique = unique;
            this.score = score;
            this.rank = rank;
        }

        /**
         * 创建一个排行榜成员对象
         *
         * @param unique 唯一标识
         * @param score  分数
         * @return Item
         */
        public Item create(String unique, double score) {
            return new Item(unique, score);
        }

        /**
         * 创建一个排行榜成员对象
         *
         * @param unique 唯一标识
         * @param score  分数
         * @param weight 权重，分数相同时会按照权重排序
         * @return Item
         */
        public Item create(String unique, int score, int weight) {
            if ((score + String.valueOf(weight)).length() > 15) {
                log.warn("score与weight组合，有效数字位已超出15位，精度会丢失。");
            }
            double scoreD = Double.parseDouble(score + "." + weight);
            return new Item(unique, scoreD);
        }

        /**
         * 获取无权重的原始分数（整数部分）
         *
         * @return 无权重的原始分数，如果分数为null则返回null
         */
        public Integer getScoreWithoutWeight() {
            if (score == null) {
                return null;
            }
            // 提取浮点数的整数部分作为无权重的原始分数
            return (int) Math.floor(score);
        }
    }

    public enum OrderBy {
        /**
         * 从小到大
         */
        ASC,
        /**
         * 从大到小
         */
        DESC
    }

}
